-- The addon namespace.
local addon, ns = ...

-- Define the manager class and put it in the addon namespace.
local Manager = {}
local ManagerMetatable = { __index = Manager }
ns.Manager = Manager

-- Constructor.
function Manager:new(shieldBarrierIcon, shieldBlockIcon, shieldChargeIcon)
	local self = {}
	setmetatable(self, ManagerMetatable)
	
	-- Initialize variables.
	self.Initialized = false
	self.Active = false
	self.PlayerName = UnitName("player") 
	self.CurrentRage = 0
	self.TotalBlocked = 0
	self.ShieldBlockActive = false
	self.BlockedAmounts = ns.LinkedList:new()
	self.ElapsedTime = 0.0
	self.ShieldBarrierIcon = shieldBarrierIcon
	self.ShieldBlockIcon = shieldBlockIcon	
	self.ShieldChargeIcon = shieldChargeIcon
	
	local resolveSpellId = 158300
	self.ResolveSpellName, _, _ = GetSpellInfo(resolveSpellId)
	
	-- Main frame (pun intended) for listening.
	self.Main =  CreateFrame("Frame", nil, UIParent)
	
	-- Event handler stuff. handlers are not fired when the addon is inactive, 
	-- listeners are. This is just my personal terminology.
	self.Handlers = {}
	self.Listeners = {}
	
	function self.Handlers.COMBAT_LOG_EVENT_UNFILTERED(sender, ...)
		if not self.ShieldBlockIcon.Active then
			return
		end
	
		local eventType = select(2, ...)
		local target = select(9, ...)
		local spellId = select(12, ...)
					
		if eventType == "SPELL_AURA_APPLIED" and spellId == self.ShieldBlockIcon.SpellId and target == self.PlayerName then
			self.ShieldBlockActive = true
			self.TotalBlocked = 0;
		end 
		
		if eventType == "SPELL_AURA_REMOVED" and spellId == self.ShieldBlockIcon.SpellId and target == self.PlayerName then
			self.ShieldBlockActive = false
		end 
		
		if eventType == "SWING_DAMAGE" and target == self.PlayerName then
			local damage = select(12, ...) or 0
			local blocked = select(16, ...) or 0
			local absorbed = select(17, ...) or 0
			
			self.BlockedAmounts:Add(damage + blocked + absorbed)
			
			if blocked and blocked > 0 and self.ShieldBlockActive then
				self.TotalBlocked = self.TotalBlocked + blocked
			end
		end
		
		if eventType == "SPELL_DAMAGE" and target == self.PlayerName then
			local damage = select(15, ...) or 0
			local blocked = select(19, ...) or 0
			local absorbed = select(20, ...) or 0
			
			if (blocked and blocked > 0) or ShieldMaidSpellDatabase[spellId] then
				self.BlockedAmounts:Add(damage + blocked + absorbed)
			end
			
			if blocked and blocked > 0 then
				ShieldMaidSpellDatabase[spellId] = true
				
				if self.ShieldBlockActive then
					self.TotalBlocked = self.TotalBlocked + blocked
				end
			end
		end
		
		if eventType == "SWING_MISSED" and target == self.PlayerName then
			local missType = select(12, ...)
			local absorbed = select(15, ...) or 0
			if missType == "ABSORB" then
				self.BlockedAmounts:Add(absorbed)
			end
		end
		
		if eventType == "SPELL_MISSED" and target == self.PlayerName then
			local missType = select(15, ...)
			local absorbed = select(18, ...) or 0
			if missType == "ABSORB" and ShieldMaidSpellDatabase[spellId] then
				self.BlockedAmounts:Add(absorbed)
			end
		end
	end

	function self.Handlers.UNIT_POWER(sender, ...)
		local unitId = select(1, ...)
		if unitId == "player" then
			self.CurrentRage = UnitPower("player", SPELL_POWER_RAGE)
		end
	end

	function self.Handlers.PLAYER_REGEN_ENABLED(...)
		if ShieldMaidConfig.hiddenOutOfCombat then
			self.ShieldBarrierIcon:Hide()
			self.ShieldBlockIcon:Hide()
			self.ShieldChargeIcon:Hide()
		end
	end

	function self.Handlers.PLAYER_REGEN_DISABLED(...)
		if ShieldMaidConfig.hiddenOutOfCombat then
			self.ShieldBarrierIcon:Show()
			self.ShieldBlockIcon:Show()
			self.ShieldChargeIcon:Show()
		end
	end

	function self.Listeners.ADDON_LOADED(sender, ...)
		local name = select(1, ...)
		if name == addon then
			self:LoadDefaultConfig()
			self:LoadSpellDatabase()
		end
	end
	
	function self.Listeners.PLAYER_LOGIN(...)
		self:Load()
	end
	
	function self.Listeners.UPDATE_SHAPESHIFT_FORM(...)
		self:Load()
	end
	
	function self.Listeners.ACTIVE_TALENT_GROUP_CHANGED(...)
		self:Load()
	end
	
	return self
end

-- Calculates the estimated absorb value.
function Manager:CalculateEstimatedAbsorb(rage)
	local baseAttackPower, positiveBuff, negativeBuff = UnitAttackPower("player")
	local attackPower = baseAttackPower + positiveBuff + negativeBuff
	local rageMultiplier = max(20, min(60, rage)) / 20.0
	local _, _, _, _, _, _, _, _, _, _, _, _, _, _, resolvePercentage, _ = UnitAura("player", self.ResolveSpellName)
	local resolveMultiplier = 1 + (resolvePercentage or 0) / 100.0
	local versatilityPercentage = GetCombatRatingBonus(CR_VERSATILITY_DAMAGE_DONE)
	local versatilityBonusPercentage = GetVersatilityBonus(CR_VERSATILITY_DAMAGE_DONE)
	local versatilityMultiplier = 1 + (versatilityPercentage + versatilityBonusPercentage) / 100.0
	return attackPower * 1.125 * rageMultiplier * resolveMultiplier 
end	

-- Calculates the estimated block based on entries from the past 6 seconds in the linked list.
-- At the same time, entries more than 6 seconds old are removed.
function Manager:CalculateEstimatedBlock()
	local sum = self.BlockedAmounts:Sum(6)
	local mastery, _ = GetMasteryEffect()
	local criticalBlockChance = mastery / 100
	local criticalBlock = sum * criticalBlockChance * 0.6
	local normalBlock = sum * (1 - criticalBlockChance) * 0.3
	return criticalBlock + normalBlock
end

-- Tick function, called in updateInterval intervals.
function Manager:Tick()
	if self.ShieldBarrierIcon.Active and self.ShieldBlockIcon.Active then
		local estimatedAbsorb = self:CalculateEstimatedAbsorb(self.CurrentRage)
		local estimatedAbsorb60 = self:CalculateEstimatedAbsorb(60)
		local estimatedBlock = self:CalculateEstimatedBlock()
		self.ShieldBarrierIcon:Update(self.CurrentRage, estimatedAbsorb, estimatedAbsorb60, estimatedBlock)
		self.ShieldBlockIcon:Update(self.CurrentRage, self.TotalBlocked, estimatedAbsorb60, estimatedBlock)
	end
	
	if self.ShieldChargeIcon.Active then
		self.ShieldChargeIcon:Update(self.CurrentRage)
	end
end

-- Enables/disables the addon depending on class and spec. 
function Manager:Load()
	local _, class = UnitClassBase("player")
	local spec = GetSpecialization()
	local stance = GetShapeshiftForm(true);
	if class == "WARRIOR" and spec == 3 then
		self.Active = true
		
		if stance == 2 and not self.ShieldBarrierIcon.Active and not self.ShieldBlockIcon.Active then
			self.ShieldBarrierIcon:Reload()
			self.ShieldBlockIcon:Reload()
		
			self.ShieldBarrierIcon.Active = true
			self.ShieldBlockIcon.Active = true
			self.ShieldChargeIcon.Active = false
			
			-- In case we reload or change spec in the middle of combat, we check for it here.
			if UnitAffectingCombat("player") or not ShieldMaidConfig.hiddenOutOfCombat then
				self.ShieldBarrierIcon:Show()
				self.ShieldBlockIcon:Show()
			else
				self.ShieldBarrierIcon:Hide()
				self.ShieldBlockIcon:Hide()
			end
			
			self.ShieldChargeIcon:Hide()
			
			ns.Console.Write("|c0000FF00Loaded|r |c00949494(protection mode)|r")
		end
		
		if stance == 1 and not self.ShieldChargeIcon.Active then
			self.ShieldChargeIcon:Reload()
		
			self.ShieldBarrierIcon.Active = false
			self.ShieldBlockIcon.Active = false
			self.ShieldChargeIcon.Active = true
			
			-- In case we reload or change spec in the middle of combat, we check for it here.
			if UnitAffectingCombat("player") or not ShieldMaidConfig.hiddenOutOfCombat then
				self.ShieldChargeIcon:Show()
			else
				self.ShieldChargeIcon:Hide()
			end
			
			self.ShieldBarrierIcon:Hide()
			self.ShieldBlockIcon:Hide()
			
			ns.Console.Write("|c0000FF00Loaded|r |c00949494(gladiator mode)|r")
		end
		
		self:Tick()
		
	elseif self.Active then
		self.Active = false
		self.ShieldBarrierIcon:Hide()
		self.ShieldBlockIcon:Hide()
		self.ShieldChargeIcon:Hide()
		
		ns.Console.Write("|c00FF0000Unloaded|r")
	end
end

-- Handler that makes sure the Tick function is called every ns.Config.updateInterval seconds.
function Manager:OnUpdateHandler(sender, seconds)
	self.ElapsedTime = self.ElapsedTime + seconds
	while self.Active and self.ElapsedTime > ns.Config.updateInterval do
		self:Tick()
		self.ElapsedTime = self.ElapsedTime - ns.Config.updateInterval
	end
end

-- OnEvent handler that delegates to the correct handler. If inactive, we only handle ACTIVE_TALENT_GROUP_CHANGED.
function Manager:OnEventHandler(sender, event, ...)
	if self.Active and self.Handlers[event] then
		self.Handlers[event](sender, ...)
	end
	if self.Listeners[event] then
		self.Listeners[event](sender, ...)
	end
end

-- Registers update and event handlers.
function Manager:RegisterHandlers()
	for k, v in pairs(self.Handlers) do
		self.Main:RegisterEvent(k);
	end
	
	for k, v in pairs(self.Listeners) do
		self.Main:RegisterEvent(k);
	end

	self.Main:SetScript("OnUpdate", function(...) self:OnUpdateHandler(...) end)
	self.Main:SetScript("OnEvent", function(...) self:OnEventHandler(...) end)
end

-- Set the default configuration if no previous configuration has been saved.
function Manager:LoadSpellDatabase()
	if not ShieldMaidSpellDatabase then
		ShieldMaidSpellDatabase = {}
	end
end

-- Set the default configuration if no previous configuration has been saved.
function Manager:LoadDefaultConfig()
	if not ShieldMaidConfig then
		ShieldMaidConfig = {}
	end

	for k, v in pairs(ns.Config.Default) do
		if ShieldMaidConfig[k] == nil then
			ShieldMaidConfig[k] = v
		end
	end
end

-- Set the default configuration if no previous configuration has been saved.
function Manager:Reset()
	ShieldMaidConfig = {}
	for k, v in pairs(ns.Config.Default) do
		ShieldMaidConfig[k] = v
	end
	
	self.ShieldBarrierIcon:Reload()
	self.ShieldBlockIcon:Reload()
	self.ShieldChargeIcon:Reload()
end

-- Locks the icons.
function Manager:Lock()
	self.ShieldBarrierIcon:Lock()
	self.ShieldBlockIcon:Lock()
	self.ShieldChargeIcon:Lock()
end

-- Unlocks the icons for dragging.
function Manager:Unlock()
	self.ShieldBarrierIcon:Unlock()
	self.ShieldBlockIcon:Unlock()
	self.ShieldChargeIcon:Unlock()
end

-- Sets the icon sizes.
function Manager:SetSize(size)
	ShieldMaidConfig.size = size
	self.ShieldBarrierIcon:Reload()
	self.ShieldBlockIcon:Reload()
	self.ShieldChargeIcon:Reload()
end

-- Sets the icon scale.
function Manager:SetScale(scale)
	ShieldMaidConfig.scale = scale
	self.ShieldBarrierIcon:Reload()
	self.ShieldBlockIcon:Reload()
	self.ShieldChargeIcon:Reload()
end

-- Sets the icon margin.
function Manager:SetMargin(margin)
	ShieldMaidConfig.margin = margin
	self.ShieldBarrierIcon:Reload()
	self.ShieldBlockIcon:Reload()
	self.ShieldChargeIcon:Reload()
end

-- Sets a value indicating whether to hide the icons when out of combat.
function Manager:SetHiddenOutOfCombat(value)
	ShieldMaidConfig.hiddenOutOfCombat = value
	if UnitAffectingCombat("player") or not ShieldMaidConfig.hiddenOutOfCombat then
		self.ShieldBarrierIcon:Show()
		self.ShieldBlockIcon:Show()
		self.ShieldChargeIcon:Show()
	else
		self.ShieldBarrierIcon:Hide()
		self.ShieldBlockIcon:Hide()
		self.ShieldChargeIcon:Hide()
	end
end

-- Sets a value indicating whether to show the icon frames.
function Manager:SetShowFrames(value)
	ShieldMaidConfig.showFrames = value
	self.ShieldBarrierIcon:Reload()
	self.ShieldBlockIcon:Reload()
	self.ShieldChargeIcon:Reload()
end

-- Sets a value indicating whether to show the glow.
function Manager:SetShowGlow(value)
	ShieldMaidConfig.showGlow = value
	if not value then
		self.ShieldBarrierIcon:HideGlow()
		self.ShieldBlockIcon:HideGlow()
	end
end

-- Sets a value indicating whether to show the cooldown "clock" on icons.
function Manager:SetShowCooldown(value)
	ShieldMaidConfig.showCooldown = value
end